import { IBus } from "../api/bus";
import { ICartridge, Mirror } from "../api/cartridge";
import { IRAM } from "../api/ram";
import { uint16 } from "../api/types";

/**
 * PPU的数据读写
 */
class PPUBus implements IBus {
    cartridge: ICartridge;
    ram: IRAM;  //2k内存
    backgroundPallette: IRAM //16b
    spritePallette: IRAM; //16b

    constructor(
        cartridge: ICartridge,
        ram: IRAM,
        backgroundPallete: IRAM,
        spritePallette: IRAM
    ) {
        this.cartridge = cartridge
        this.ram = ram;
        this.backgroundPallette = backgroundPallete;
        this.spritePallette = spritePallette
    }


    writeByte(address: number, data: number): void {
        address &= 0x3FFF;

        if (address < 0x2000) {
            // Pattern table 0 - 1 写入Chr
            this.cartridge.mapper.write(address, data);
        } else if (address < 0x3000) {
            // Nametable 0 - 3     命名表
            this.ram.write(this.parseMirrorAddress(address), data);
        } else if (address < 0x3F00) { // 镜像
            // Mirrors of $2000-$2EFF
            return this.writeByte(address - 0x1000, data);
        } else {
            // Palette RAM indexes  颜色板
            address &= 0x3F1F;

            if (address < 0x3F10) { // Background pallette
                this.backgroundPallette.write(address, data);
            } else { // Sprite pallette
                // Refer to https://wiki.nesdev.com/w/index.php/PPU_palettes
                // Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C
                if (!(address & 0b11)) {
                    address -= 0x10;
                    return this.backgroundPallette.write(address, data);
                }

                this.spritePallette.write(address, data);
            }
        }
    }
    writeWord(address: number, data: number): void {
        this.writeByte(address, data);
        this.writeByte(address + 1, data >> 8)
    }
    readByte(address: number): number {
        if (address < 0x2000) {
            // Pattern table 0 - 1 读取Chr
            return this.cartridge.mapper.read(address);
        } else if (address < 0x3000) {
            // Nametable 0 - 3     读取命令表和属性表
            return this.ram.read(this.parseMirrorAddress(address));
        } else if (address < 0x3F00) {
            // Mirrors of $2000-$2EFF //镜像读取
            return this.readByte(address - 0x1000);
        } else {
            // Palette RAM indexes 颜色板索引
            address &= 0x3F1F;

            if (address < 0x3F10) { // Background pallette 背景颜色板
                return this.backgroundPallette.read(address);
            } else { // Sprite pallette
                // Refer to https://wiki.nesdev.com/w/index.php/PPU_palettes
                // Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C
                if (!(address & 0b11)) {
                    address -= 0x10;
                    return this.backgroundPallette.read(address);
                }
                //精灵颜色板
                return this.spritePallette.read(address);
            }
        }
    }
    readWord(address: number): number {
        return this.readByte(address + 1) << 8 | this.readByte(address);
    }


    // 根据卡带的镜像配置
    private parseMirrorAddress(address: uint16): uint16 {
        switch (this.cartridge.info.mirror) {
            case Mirror.HORIZONTAL:
                return (address & 0b0010_0011_1111_1111) | (address & 0b0000_1000_0000_0000 ? 0b0000_0100_0000_0000 : 0);
            case Mirror.VERTICAL:
                return address & 0x27FF;
            case Mirror.FOUR_SCREEN:
                return address;
            case Mirror.SINGLE_SCREEN_LOWER_BANK:
                return address & 0x23FF;
            case Mirror.SINGLE_SCREEN_UPPER_BANK:
                return address & 0x23FF + 0x0400;
            default:
                throw new Error(`Invalid mirror type: '${this.cartridge.info.mirror}'`);
        }
    }
}

export {
    PPUBus
}